﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using matrixdotnet.Common;
using matrixdotnet.DBHelperLib;
using System.Data.Common;
using matrixdotnet.CommonDAL;

namespace matrixdotnet.CommonDALImp
{
    public abstract class DataMapper<
        TIdentity, TUniqueKey, TBusinessEntity, TMappedDataEntity, TMappedDataEntityInterface, TLazyEntity> :
        IBusinessEntityFinder<TUniqueKey, TBusinessEntity>,
        IDataEntityFinder<TIdentity, TUniqueKey, TBusinessEntity, TMappedDataEntity>,
        IFromDataEntityObjectMapper<TIdentity, TUniqueKey, TBusinessEntity, TMappedDataEntity>,
        IToBusinessObjectMapper<TIdentity, TUniqueKey, TBusinessEntity>
        where TIdentity : struct
        where TUniqueKey : struct
        where TBusinessEntity : class, IBusinessEntity<TUniqueKey>
        where TMappedDataEntityInterface : class, IDataEntity<TIdentity, TUniqueKey, TBusinessEntity>, TBusinessEntity
        where TMappedDataEntity : MappedDataEntity<TIdentity, TUniqueKey, TBusinessEntity, TMappedDataEntity>, TMappedDataEntityInterface, TBusinessEntity
        where TLazyEntity : LazyDataEntity<TIdentity, TUniqueKey, TBusinessEntity, TMappedDataEntityInterface>, TBusinessEntity
    {
        protected Dictionary<TIdentity, TMappedDataEntity> _identityMap = 
            new Dictionary<TIdentity, TMappedDataEntity>();

        protected Dictionary<TUniqueKey, TMappedDataEntity> _uniqueKeyMap =
            new Dictionary<TUniqueKey, TMappedDataEntity>();

        protected abstract IDataEntityFactory<
            TIdentity, TUniqueKey, TBusinessEntity, TMappedDataEntity, TMappedDataEntityInterface, TLazyEntity>
            DataEntityFactory { get; }

        protected DBHelper DbHelper { get; set; }

        public DataMapper(DBHelper helper)
        {
            DbHelper = helper;
        }

        public virtual TMappedDataEntity FindDataEntity(TIdentity identity)
        {
            TMappedDataEntity entity = Lookup(identity);

            if (entity != null)
                return entity;

            SetIdentityParameters(identity);

            DbDataReader reader = ExecuteFindByIdentityReader();
            ToDataObjectMapper mapper = CreateToDataObjectMapper();
            return mapper.MapFull(reader);
        }

        public virtual TMappedDataEntity FindDataEntity(TUniqueKey uniqueKey)
        {
            TMappedDataEntity entity = null;

            if (_uniqueKeyMap.TryGetValue(uniqueKey, out entity))
                return entity;

            SetUniqueKeyParameters(uniqueKey);

            DbDataReader reader = ExecuteFindByUniqueKeyReader();
            ToDataObjectMapper mapper = CreateToDataObjectMapper();
            return mapper.MapFull(reader);
        }

        public virtual TBusinessEntity Find(TUniqueKey uniqueKey)
        {
            return FindDataEntity(uniqueKey);
        }

        public virtual int Update(TMappedDataEntity dataEntity)
        {
            if (!_identityMap.ContainsKey(dataEntity.Identity.Value))
                throw new ApplicationException(
                    "The requested for update data entity is not in the identiy map: " + dataEntity.GetType().ToString());

            int rowsAffected = UpdateEntity(dataEntity);

            if (rowsAffected > 1)
                throw new ApplicationException(
                    "Bad update query for " + dataEntity.GetType().ToString());

            dataEntity.Version++;

            return rowsAffected;
        }

        public virtual TIdentity Insert(TMappedDataEntity dataEntity)
        {
            SetBusinessEntityParameters(dataEntity.BusinessEntity);
            SetVersionParameter(dataEntity.Version);

            dataEntity.Identity = ExecuteInsert();
            AddEntity(dataEntity);

            return dataEntity.Identity.Value;
        }

        public virtual int Delete(TMappedDataEntity dataEntity)
        {
            throw new NotImplementedException();
        }

        public virtual TBusinessEntity Map(TIdentity identity, DbDataReader reader)
        {
            ToDataObjectMapper mapper = CreateToDataObjectMapper();
            return mapper.Map(identity, reader);
        }

        protected virtual int OptimisticConcurrencyUpdate(
            TMappedDataEntity dataEntity,
            string oldVersionParamName,
            string newVersionParamName)
        {
            SetBusinessEntityParameters(dataEntity.BusinessEntity);

            DbHelper.Command.Parameters.Add(
                DbHelper.CreateParameter(newVersionParamName, dataEntity.Version + 1));

            SetIdentityParameters(dataEntity.Identity.Value);

            DbHelper.Command.Parameters.Add(
                DbHelper.CreateParameter(oldVersionParamName, dataEntity.Version));

            int rowsAffected = ExecuteUpdate();

            if (rowsAffected == 0)
                throw new OptimisticConcurrencyException(
                    dataEntity, "Failed to update " + dataEntity.GetType().ToString());

            return rowsAffected;
        }

        protected void SetIdentityParameter<T>(T identity)
        {
            SetIdentityParameter("@Identity", identity);
        }

        protected void SetIdentityParameter<T>(string paramName, T identity)
        {
            DbHelper.Command.Parameters.Add(DbHelper.CreateParameter(paramName, identity));
        }

        private TMappedDataEntity Lookup(TIdentity identity)
        {
            TMappedDataEntity entity = null;

            return _identityMap.TryGetValue(identity, out entity) ? entity : null;
        }

        private void AddEntity(TMappedDataEntity entity)
        {
            _identityMap.Add(entity.Identity.Value, entity);
            _uniqueKeyMap.Add(entity.UniqueKey, entity);
        }

        protected delegate T FullEntityCast<T>(TMappedDataEntity entity) where T : TBusinessEntity;
        protected delegate T LazyEntityCast<T>(TLazyEntity entity) where T : TBusinessEntity;

        protected abstract class ToDataObjectMapper : IToBusinessObjectMapper<TIdentity, TUniqueKey, TBusinessEntity>
        {
            protected static IDataEntityFactory<
                TIdentity, TUniqueKey, TBusinessEntity, TMappedDataEntity, TMappedDataEntityInterface, TLazyEntity>
                DataEntityFactory { get; set; }

            private int _versionOrd = -1;

            protected abstract DataMapper<
                TIdentity, TUniqueKey, TBusinessEntity, TMappedDataEntity, TMappedDataEntityInterface, TLazyEntity> DataMapper { get; }
            protected bool OrdinalsSet { get; set; }

            protected static TMappedDataEntity FullToFull(TMappedDataEntity entity)
            {
                return entity;
            }
            protected static TBusinessEntity FullToBusiness(TMappedDataEntity entity)
            {
                return entity;
            }
            protected static TBusinessEntity LazyToBusiness(TLazyEntity lazy)
            {
                return lazy;
            }

            public virtual TMappedDataEntity MapFull(DbDataReader reader)
            {
                return MapSingle(reader, new FullEntityCast<TMappedDataEntity>(FullToFull), null);
            }

            public virtual TBusinessEntity Map(DbDataReader reader)
            {
                return MapSingle(
                    reader,
                    new FullEntityCast<TBusinessEntity>(FullToBusiness),
                    new LazyEntityCast<TBusinessEntity>(LazyToBusiness));
            }

            public virtual void MapMany(DbDataReader reader, ICollection<TBusinessEntity> many)
            {
                MapMany(
                    reader,
                    new FullEntityCast<TBusinessEntity>(FullToBusiness),
                    new LazyEntityCast<TBusinessEntity>(LazyToBusiness),
                    many);
            }

            public TBusinessEntity Map(TIdentity identity, DbDataReader reader)
            {
                return MapWithLookup(
                    identity,
                    reader,
                    new FullEntityCast<TBusinessEntity>(FullToBusiness),
                    new LazyEntityCast<TBusinessEntity>(LazyToBusiness));
            }

            protected virtual T MapSingle<T>(
                DbDataReader reader, FullEntityCast<T> fullCast, LazyEntityCast<T> lazyCast)
                where T : TBusinessEntity
            {
                if (!reader.Read())
                {
                    reader.Close();
                    return default(T);
                }

                try
                {
                    SetIdentityOrdinals(reader);
                    return MapNoLookup(reader, fullCast, lazyCast);
                }
                finally
                {
                    reader.Close();
                    OrdinalsSet = false;
                }
            }

            protected virtual void MapMany<T>(
                DbDataReader reader,
                FullEntityCast<T> fullCast,
                LazyEntityCast<T> lazyCast,
                ICollection<T> many)

                where T : TBusinessEntity
            {
                if (!reader.Read())
                {
                    reader.Close();
                    return;
                }

                try
                {
                    SetIdentityOrdinals(reader);
                    do
                        many.Add(MapWithLookup(reader, fullCast, lazyCast));
                    while (reader.Read());
                }
                finally
                {
                    reader.Close();
                    OrdinalsSet = false;
                }
            }

            protected virtual T MapWithLookup<T>(
                DbDataReader reader, FullEntityCast<T> fullCast, LazyEntityCast<T> lazyCast)
                where T : TBusinessEntity
            {
                TIdentity identity = MapIdentity(reader);

                return MapWithLookup(identity, reader, fullCast, lazyCast);
            }

            protected virtual T MapWithLookup<T>(
                TIdentity identity, DbDataReader reader, FullEntityCast<T> fullCast, LazyEntityCast<T> lazyCast)
                where T : TBusinessEntity
            {
                TMappedDataEntity entity = DataMapper.Lookup(identity);
                if (entity != null)
                    return fullCast(entity);

                return Map(identity, reader, fullCast, lazyCast);
            }

            protected virtual T MapNoLookup<T>(DbDataReader reader, FullEntityCast<T> fullCast, LazyEntityCast<T> lazyCast)
                where T : TBusinessEntity
            {
                TIdentity identity = MapIdentity(reader);
                return Map(identity, reader, fullCast, lazyCast);
            }

            protected virtual T Map<T>(
                TIdentity identity,
                DbDataReader reader,
                FullEntityCast<T> fullCast,
                LazyEntityCast<T> lazyCast)
                where T : TBusinessEntity
            {
                if (!OrdinalsSet)
                    SetOrdinals(reader);

                if (IsFull)
                {
                    TBusinessEntity businessEntity = MapBusinessEntity(reader);
                    if (businessEntity == null)
                        throw new ApplicationException("Unexpected failure while mapping the business entity");

                    long version = reader.GetInt64(_versionOrd);
                    TMappedDataEntity entity = DataMapper.DataEntityFactory.Create(identity, businessEntity, version);

                    DataMapper.AddEntity(entity);

                    return fullCast(entity);
                }
                else
                {
                    if (lazyCast == null)
                        throw new ApplicationException("The entity is not full");

                    TLazyEntity lazy = DataMapper.DataEntityFactory.Create(identity);

                    return lazyCast(lazy);
                }
            }

            protected virtual void SetOrdinals(DbDataReader reader)
            {
                _versionOrd = reader.GetOrdinal(DataMapper.VersionColumnName);

                if (_versionOrd >= 0)
                    SetBusinessOrdinals(reader);

                OrdinalsSet = true;
            }

            protected virtual bool IsFull
            {
                get
                {
                    if (!OrdinalsSet)
                        throw new ApplicationException("Ordinals not set");

                    return _versionOrd >= 0;
                }
            }

            protected abstract void SetIdentityOrdinals(DbDataReader reader);
            protected abstract TIdentity MapIdentity(DbDataReader reader);
            protected abstract void SetBusinessOrdinals(DbDataReader reader);
            protected abstract TBusinessEntity MapBusinessEntity(DbDataReader reader);
        }

        protected virtual void SetVersionParameter(long version)
        {
            DbHelper.Command.Parameters.Add(
                DbHelper.CreateParameter("@" + VersionColumnName, version));
        }

        protected abstract ToDataObjectMapper CreateToDataObjectMapper();
        protected abstract int UpdateEntity(TMappedDataEntity entity);
        protected abstract void SetIdentityParameters(TIdentity identity);
        protected abstract void SetUniqueKeyParameters(TUniqueKey uniqueKey);
        protected abstract void SetBusinessEntityParameters(TBusinessEntity businessEntity);
        protected abstract DbDataReader ExecuteFindByIdentityReader();
        protected abstract DbDataReader ExecuteFindByUniqueKeyReader();
        protected abstract int ExecuteUpdate();
        protected abstract TIdentity ExecuteInsert();
        protected abstract string VersionColumnName { get; }


    }

    public class MyDataMapper : DataMapper<long, long, IMyBusinessEntity, MyDataEntity, IMyDataEntity, MyLazyEntity>
    {
        public MyDataMapper() : base(null)
        {

        }

        protected override DbDataReader ExecuteFindByIdentityReader()
        {
            throw new NotImplementedException();
        }

        protected override void SetIdentityParameters(long identity)
        {
            throw new NotImplementedException();
        }

        protected override void SetUniqueKeyParameters(long uniqueKey)
        {
            throw new NotImplementedException();
        }

        protected override DbDataReader ExecuteFindByUniqueKeyReader()
        {
            throw new NotImplementedException();
        }

        protected override void SetBusinessEntityParameters(IMyBusinessEntity businessEntity)
        {
            throw new NotImplementedException();
        }

        protected override int ExecuteUpdate()
        {
            throw new NotImplementedException();
        }

        protected override int UpdateEntity(MyDataEntity entity)
        {
            throw new NotImplementedException();
        }

        protected override void SetVersionParameter(long version)
        {
            throw new NotImplementedException();
        }

        protected override long ExecuteInsert()
        {
            throw new NotImplementedException();
        }

        protected override IDataEntityFactory<long, long, IMyBusinessEntity, MyDataEntity, IMyDataEntity, MyLazyEntity> DataEntityFactory
        {
            get { throw new NotImplementedException(); }
        }

        protected override string VersionColumnName
        {
            get { throw new NotImplementedException(); }
        }

        protected override /*DataMapper<long, long, IMyBusinessEntity, MyDataEntity, MyLazyEntity>.*/ ToDataObjectMapper CreateToDataObjectMapper()
        {
            throw new NotImplementedException();
        }
    }
}
